Log inGet started

physics

Updated October 20, 2026

Lowercase alias of Physics. Same identity (#565). Prefer this in new code so the convention matches entity, scene, cam, ...

physics: any

The physics namespace: 80 functions.

globals/physics/COLLIDER_COMPONENTS

physics.COLLIDER_COMPONENTS()

Every collider component, in the order a lookup walks them. Shape is the component's identity, so code that works on "whatever collider this entity has" walks this list rather than guessing a shape.

for _, name in ipairs(Physics.COLLIDER_COMPONENTS) do ... end

globals/physics/addCollider

physics.addCollider(entityId: string | entityRef, component: string, config: table?)

Add a collider component to an entity, naming the shape you want.

Parameters

  • entityId string | entityRef — Target entity id.
  • component string — One of Physics.COLLIDER_COMPONENTS.
  • config table (optional) — The component's own fields, e.g. { radius = 0.5 } for a sphere.
Physics.addCollider(id, "SphereCollider", { radius = 0.5 })

globals/physics/addConstraint

physics.addConstraint(entityId: string | entityRef, opts: table?)

Add a transform constraint to an entity.

Parameters

  • entityId string | entityRef — Target entity id.
  • opts table (optional) — Optional constraint description (targetEntityId, position, rotation, scale, lookAt, targetPosition, axes, weight).
Physics.addConstraint(id, { targetEntityId = parent, position = true })

globals/physics/addJoint

physics.addJoint(entityIdA: string | entityRef, entityIdB: string | entityRef, opts: table?)

Add a Joint component connecting two entities. Accepts either vec3-style anchor inputs (localAnchor = {x,y,z}) or pre-split scalar keys (localAnchorX/Y/Z).

Parameters

  • entityIdA string | entityRef — Entity that hosts the Joint component.
  • entityIdB string | entityRef — Connected entity.
  • opts table (optional) — Optional joint description (kind, anchors, axis, stiffness, damping, restLength, maxDistance, breakForce, breakTorque, region, remoteRegion). Either end may be a soft body (an entity with a SoftBody component): region anchors this end on a region of entity A's soft body and remoteRegion the other end on a region of B's, each as Physics.softRegion takes it ({ box = { min, max } } or { nearest = point }); an end with no region is anchored on the soft body as a whole. The anchor of a region end is measured from the centre of its particles.
Physics.addJoint(a, b, { kind = "fixed" })
Physics.addJoint(a, b, { kind = "hinge", axis = {x=0,y=1,z=0} })
Physics.addJoint(a, b, { kind = "rope", maxDistance = 8 })
Physics.addJoint(a, b, { kind = "fixed", breakForce = 1200, breakTorque = 800 })
Physics.addJoint(bag, hook, { kind = "ball", region = { nearest = {0, 0.5, 0} } })
Physics.addJoint(jellyA, jellyB, { kind = "fixed", region = { box = { min = {0.4, -0.5, -0.5}, max = {0.5, 0.5, 0.5} } }, remoteRegion = { box = { min = {-0.5, -0.5, -0.5}, max = {-0.4, 0.5, 0.5} } } })

globals/physics/addVelocity

physics.addVelocity(a: string | entityRef | number | vec3, b: (number | vec3)?, c: number?, d: number?)

Add to the linear velocity of an entity. Same call shapes as setVelocity.

Parameters

  • a string | entityRef | number | vec3 — dx, a {x, y, z} delta vector, or an entity id (explicit target).
  • b (number | vec3) (optional) — dy, dx, or the delta vector depending on call form.
  • c number (optional) — dz or dy depending on call form.
  • d number (optional) — Optional dz when targeting an explicit entity.
Physics.addVelocity(0, 5, 0)
Physics.addVelocity(entityId, 0, 5, 0)
Physics.addVelocity(entityId, {x=0, y=5, z=0})

globals/physics/addWheelCollider

physics.addWheelCollider(entityId: string | entityRef, config: table?)

Add a WheelCollider to an entity. The entity must be a child (or descendant) of a rigid body — the system walks up the hierarchy to find the Physics component. Every wheel under one body is a wheel of that body's ray-cast vehicle.

Parameters

  • entityId string | entityRef — Target entity id.
  • config table (optional) — Optional wheel configuration (radius?, suspensionDistance?, springRate?, damperRate?, targetPosition?, maxSuspensionForce?, motorTorque?, brakeTorque?, steerAngle?, forwardFriction?, sidewaysFriction?, frictionSlip?, sideFrictionStiffness?, rollingResistance?, width?, is2D?, visual?).
Physics.addWheelCollider(id, { radius = 0.35, motorTorque = 500 })

globals/physics/applyForce

physics.applyForce(entityIdOrForce: string | entityRef | vec3, force: vec3?, region: PhysicsSoftRegion?)

Apply a force to an entity's rigid or soft body for the next physics step — call every frame for continuous thrust. With one argument the script-context entity is targeted; with two args the explicit entity id wins. A soft body takes the force spread over its particles by mass, so it accelerates as a whole the way a rigid body of its mass would; region puts it on part of the body instead ({ box = { min, max } } or { nearest = point }, as Physics.softRegion takes it), which accelerates that part alike and leaves the rest to follow.

Parameters

  • entityIdOrForce string | entityRef | vec3 — Entity id (when paired with force) OR a force vector for the script-context entity.
  • force vec3 (optional) — Optional force vector when targeting an explicit entity.
  • region PhysicsSoftRegion (optional) — Part of a soft body to push, as Physics.softRegion takes it; omit for the whole body.
Physics.applyForce({x=0, y=10, z=0})
Physics.applyForce(entityId, {x=0, y=10, z=0})
Physics.applyForce(cloth, {x=0, y=0, z=4}, { box = { min = {-0.5, -1, 0.4}, max = {0.5, 1, 0.5} } })

globals/physics/applyForceAtPoint

physics.applyForceAtPoint(entityId: string | entityRef, force: vec3, point: vec3, opts: PhysicsPushOptions?)

Apply a force at a specific world-space point for the next physics step. On a rigid body it generates the matching torque from the lever arm. On a soft body it pushes the particles around the point, weighted from the whole at the point to nothing at opts.radius (by default twice the spacing of the body's particles), so the body gives way there first. It always reaches at least the four particles nearest the point, the smallest patch a body can dent, so a radius finer than the particles are spaced spreads over that patch.

Parameters

  • entityId string | entityRef — Target entity id.
  • force vec3 — Force vector, in newtons.
  • point vec3 — World-space application point.
  • opts PhysicsPushOptions (optional) — { radius: number? }: how far into a soft body the force reaches, in metres.
Physics.applyForceAtPoint(id, {x=0,y=10,z=0}, {x=1,y=0,z=0})
Physics.applyForceAtPoint(jelly, {x=0,y=-40,z=0}, {x=0,y=1.5,z=0}, { radius = 0.3 })

globals/physics/applyImpulse

physics.applyImpulse(entityIdOrImpulse: string | entityRef | vec3, impulse: vec3?, region: PhysicsSoftRegion?)

Apply an instantaneous impulse (one-shot velocity change). With one argument the script-context entity is targeted; with two args the explicit entity id wins. A soft body takes the impulse spread over its particles by mass, so it moves off as a whole the way a rigid body of its mass would; region puts it on part of the body instead (as Physics.softRegion takes it), which sets that part moving and leaves the rest to follow. applyImpulseAtPoint aims one at a point.

Parameters

  • entityIdOrImpulse string | entityRef | vec3 — Entity id (with impulse) OR an impulse vector for the script-context entity.
  • impulse vec3 (optional) — Optional impulse vector when targeting an explicit entity.
  • region PhysicsSoftRegion (optional) — Part of a soft body to push, as Physics.softRegion takes it; omit for the whole body.
Physics.applyImpulse({x=0, y=5, z=0})
Physics.applyImpulse(entityId, {x=0, y=5, z=0})
Physics.applyImpulse(jelly, {x=0, y=-2, z=0}, { box = { min = {-0.5, 0.4, -0.5}, max = {0.5, 0.5, 0.5} } })

globals/physics/applyImpulseAtPoint

physics.applyImpulseAtPoint(entityId: string | entityRef, impulse: vec3, point: vec3, opts: PhysicsPushOptions?)

Apply an instantaneous impulse at a world-space point: a hit, a kick, a crash. On a rigid body it changes the body's velocity and spin as an impulse at that point of it would. On a soft body it pushes the particles around the point, weighted from the whole at the point to nothing at opts.radius (by default twice the spacing of the body's particles), so the body dents there and the rest of it is carried along through its elastic elements. It always reaches at least the four particles nearest the point, the smallest patch a body can dent, so a radius finer than the particles are spaced spreads over that patch; a finer dent takes a finer body (a higher resolution). The impulse is the momentum the body gains, in newton-seconds, wherever it lands.

Parameters

  • entityId string | entityRef — The body's entity id or proxy.
  • impulse vec3 — The impulse, in newton-seconds.
  • point vec3 — Where it lands, in world space: a contact point.
  • opts PhysicsPushOptions (optional) — { radius: number? }: how far into a soft body the impulse reaches, in metres.
Physics.applyImpulseAtPoint(crate, {x=0, y=0, z=-30}, hit.point)
local contact = Physics.contacts(shell)[1]; if contact then Physics.applyImpulseAtPoint(shell, {x=-60, y=0, z=0}, contact.points[1].point, { radius = 0.4 }) end

globals/physics/applyRadialImpulse

physics.applyRadialImpulse(entityId: string | entityRef, center: vec3, magnitude: number, opts: PhysicsPushOptions?)

Push a body straight away from a world point, like a blast: the push is magnitude newton-seconds at the centre, weighted to nothing at opts.radius (omitted, it reaches any distance at full strength). A rigid body is pushed by the distance to its centre of mass; a soft body particle by particle, each away from the centre by its own distance, so a blast beside a balloon pushes it off and one inside a body spreads it out. Each moves off at the speed magnitude gives a body of its mass.

Parameters

  • entityId string | entityRef — The body's entity id or proxy.
  • center vec3 — The blast's world position.
  • magnitude number — The impulse at the centre, in newton-seconds.
  • opts PhysicsPushOptions (optional) — { radius: number? }: how far the push reaches, in metres.
Physics.applyRadialImpulse(balloon, {x=0, y=1, z=-1}, 8, { radius = 3 })
for _, id in pile do Physics.applyRadialImpulse(id, blast, 20, { radius = 4 }) end

globals/physics/applyTorque

physics.applyTorque(entityIdOrTorque: string | entityRef | vec3, torque: vec3?)

Apply a torque to an entity's rigid body for the next physics step — call every frame for continuous spin-up. With one argument the script-context entity is targeted; with two args the explicit entity id wins.

Parameters

  • entityIdOrTorque string | entityRef | vec3 — Entity id (with torque) OR a torque vector for the script-context entity.
  • torque vec3 (optional) — Optional torque vector when targeting an explicit entity.
Physics.applyTorque({x=0, y=1, z=0})
Physics.applyTorque(entityId, {x=0, y=1, z=0})

globals/physics/attachSoftRegion

physics.attachSoftRegion(softId: string | entityRef, targetId: string | entityRef, region: PhysicsSoftRegion?) -> number

Hold a region of a soft body to a rigid body, two-way: the region follows the rigid body wherever it goes, and the rigid body carries the region's weight and pull. Each particle in the region is tied to the point of the rigid body it stands at when the attachment is made. The attachment is an entry of the soft body's SoftBody component (attachments), so it is saved with the scene, reaches every peer, and is made again on a rebuilt body; it is released when either side is removed. Its load reads back from Physics.softBodyState(softId).attachments. A cloth's edge on a flagpole, a soft panel on a car's chassis, a jelly glued to a moving platform. Where the rigid body's colliders touch the soft body (a shell resting on its chassis), the contact and the hold push on each other: keep them clear, or Physics.ignoreCollision(softId, targetId).

Parameters

  • softId string | entityRef — The soft body's entity (it carries a SoftBody component).
  • targetId string | entityRef — The rigid body's entity (it carries Physics), by id, name or proxy.
  • region PhysicsSoftRegion (optional) — Which particles to hold, as Physics.softRegion takes it: { box = { min, max } }, { nearest = point }, with space = "world" for world coordinates; nil holds the whole body.

Returns number — The attachment's index in the component's attachments.

Physics.attachSoftRegion(flag, pole, { box = { min = {-0.5, -1, -1}, max = {-0.45, 1, 1} } })
Physics.attachSoftRegion(shell, chassis, { box = { min = {-0.5, -0.5, -0.5}, max = {0.5, -0.4, 0.5} } })

globals/physics/bodyState

physics.bodyState(entityId: string | entityRef) -> PhysicsBodyState?

Everything the solver holds for one body — its type, mass, centre of mass, inertia, gravity scale, damping, lock flags, CCD, collision groups, sleep state, velocities, the force and torque queued for the next step, its colliders, contacts, joints and transform constraints, and why it is not moving.

Parameters

  • entityId string | entityRef — Entity id or proxy.

Returns PhysicsBodyState? — A PhysicsBodyState (with exists = false for an entity that carries no rigid body, and waitingFor naming what its collider needs while that collider cannot be built), or nil when nothing in the scene answers to that id.

local b = Physics.bodyState(id); print(b.bodyType, b.mass, b.stillness)
local b = Physics.bodyState(id); if not b.exists then print(b.waitingFor or "no body was built") end

globals/physics/boxCast

physics.boxCast(origin: vec3, halfExtents: vec3, direction: vec3, maxDistance: number?, exclude: (string | {string})?) -> table?

Cast a box along a direction and return the first hit. A trigger (isTrigger) holds nothing up, so the sweep passes through it.

Parameters

  • origin vec3 — Box center at the start of the cast.
  • halfExtents vec3 — Half the size of the box on each axis.
  • direction vec3 — Cast direction, of any length: the engine normalises it, so maxDistance and distance are world units.
  • maxDistance number (optional) — Optional distance limit.
  • exclude (string | {string}) (optional) — Optional entity id, or array of entity ids, to exclude from hits. Applied while sweeping rather than to the answer, so a cast that starts inside an excluded collider reports what is behind it.

Returns table? — Hit table { entityId, point, normal, distance, startedInside, softBody? }, or nil if nothing hit. startedInside is false for a surface the query crossed on its way there, and true when the query's own start already lay inside that collider — where distance is 0, point is the start itself, and normal is the shortest way out of the collider. normal is a unit vector either way. softBody is set when what was hit is a soft body, whose surface the query meets as it is deformed now: a PhysicsSoftHit naming the surface triangle, the particles carrying it with the hit's weight on each, the particle nearest the hit, and region, the hit as a soft region to hand to Physics.applyImpulse, Physics.pinSoftRegion or Physics.softParticles. Physics.applyImpulseAtPoint takes the hit's point as it is.

local hit = Physics.boxCast(o, {x=0.5,y=0.5,z=0.5}, dir, 10)
local hit = Physics.boxCast(o, {x=0.5,y=0.5,z=0.5}, dir, 10, { selfId, carriedId })

globals/physics/capsuleCast

physics.capsuleCast(origin: vec3, radius: number, halfHeight: number, direction: vec3, maxDistance: number?, exclude: (string | {string})?) -> table?

Cast an upright capsule along a direction and return the first hit. This is the sweep that answers whether a body of that shape fits through a passage: a capsule of radius r reports a hit on anything that leaves it less than 2 * r of clearance. A trigger (isTrigger) holds nothing up, so the sweep passes through it.

Parameters

  • origin vec3 — Capsule centre at the start of the cast.
  • radius number — Capsule radius.
  • halfHeight number — Distance from the centre to either cap centre. The capsule stands halfHeight + radius tall in each direction.
  • direction vec3 — Cast direction, of any length: the engine normalises it, so maxDistance and distance are world units.
  • maxDistance number (optional) — Optional distance limit.
  • exclude (string | {string}) (optional) — Optional entity id, or array of entity ids, to exclude from hits. Applied while sweeping rather than to the answer, so a cast that starts inside an excluded collider reports what is behind it.

Returns table? — Hit table { entityId, point, normal, distance, startedInside, softBody? }, or nil if nothing hit. startedInside is false for a surface the query crossed on its way there, and true when the query's own start already lay inside that collider — where distance is 0, point is the start itself, and normal is the shortest way out of the collider. normal is a unit vector either way. softBody is set when what was hit is a soft body, whose surface the query meets as it is deformed now: a PhysicsSoftHit naming the surface triangle, the particles carrying it with the hit's weight on each, the particle nearest the hit, and region, the hit as a soft region to hand to Physics.applyImpulse, Physics.pinSoftRegion or Physics.softParticles. Physics.applyImpulseAtPoint takes the hit's point as it is.

local hit = Physics.capsuleCast(o, 0.3, 0.6, dir, 0.5)
local hit = Physics.capsuleCast(o, 0.3, 0.6, dir, 0.5, selfId)

globals/physics/colliderCount

physics.colliderCount() -> number

How many colliders the physics world holds. Zero means no ray, cast or overlap fired into this world can hit anything, so it is what separates a query that MISSED from a query fired into a world that holds nothing to hit. Read off the collider set itself, so it costs the same whatever the world holds.

Returns number — colliders across the whole physics world.

if Physics.colliderCount() == 0 then print("nothing here is solid") end

globals/physics/colliderGeometry

physics.colliderGeometry(options: table?) -> table?

Read the physics world as drawable triangles: every collider triangulated in world space into one indexed mesh, in GPU buffers ready to draw.

Box, sphere, capsule, cylinder, cone, convex, triangle-mesh and heightfield colliders return their real surface, and a compound returns its children folded together; a shape with no triangulation returns its bounding box and reports exact = false.

options.colors is POSITIONAL over colliderManifest() — entry i colours collider i — so you can colour by role, shape, entity or anything else you read there. A position you leave out takes options.defaultColor.

The returned buffers are yours: destroy them when you replace them.

Parameters

  • options table (optional) — { tessellation = "low"|"medium"|"high", colors = { {r,g,b,a}, ... }, defaultColor = {r,g,b,a} }.

Returns table? — { vertices, indices, vertexCount, indexCount, colliders } where each entry of colliders is { entity, colliderName?, shapeType, role, exact, firstIndex, indexCount }.

local geo = Physics.colliderGeometry({ tessellation = "high" })

globals/physics/colliderManifest

physics.colliderManifest() -> table

List every physics collider in the world with what it is and what it takes part in — no geometry, so it is the cheap read to make before deciding what to do with each one.

role is one of static, dynamic, kinematic, sensor. A sensor is a collider the simulation holds as one, reported ahead of the body type behind it, and a collider with no rigid body is static. exact says whether colliderGeometry would return this collider's true surface or its bounding box.

Every collider of one entity shares its entity, so this is what to key per-object decisions on. The order is stable across calls over an unchanged world, which is what makes colliderGeometry's positional colours usable.

Returns table — Array of { entity, colliderName?, shapeType, role, exact }.

for _, c in ipairs(Physics.colliderManifest()) do print(c.entity, c.role) end

globals/physics/colliderOn

physics.colliderOn(entityId: string | entityRef) -> string?

Which collider component an entity carries, or nil when it carries none.

Parameters

  • entityId string | entityRef — Target entity id.

Returns string? The component name, e.g. "SphereCollider".

local which = Physics.colliderOn(id)

globals/physics/colliderShapes

physics.colliderShapes(entityId: string | entityRef) -> table

Read an entity's resolved physics collider shape(s) as the physics engine sees them, including auto-sized colliders.

shapeType is one of box, sphere, capsule, convex, mesh, heightfield, compound, other — the shape the simulation is running, so a mesh collider reads mesh.

params carries half-extents for a box, radius for a sphere, radius and half-height for a capsule, and the collider's bounding half-extents for the shapes that have no parametric description. A convex collider reports its outline in linePoints instead.

Parameters

  • entityId string | entityRef — Target entity id.

Returns table — Array of resolved collider shapes (empty if none): { shapeType, position, rotation, params, linePoints, name? }.

local shapes = Physics.colliderShapes(id)

globals/physics/contacts

physics.contacts(entityId: string | entityRef, otherId: (string | entityRef)?) -> { PhysicsContact }

Every contact one body's colliders are in right now, with the other entity, the normal, how deeply the two interpenetrate, the impulse the last step applied, and each contact point. A pair is listed while its surfaces touch: a pair kept apart (Physics.ignoreCollision, collision groups) or no longer touching is not. For a soft body these are the contacts of its surface with every other collider, another soft body's surface included. A contact with a soft body on either side names the particles of it that are touching (particles for this side, otherParticles for the other), so a hit can be answered where it landed. Name otherId to read only the contacts with that one entity: a body resting on the ground holds a contact point per surface element that touches it, and a read of one pair costs that pair alone.

Parameters

  • entityId string | entityRef — Entity id or proxy.
  • otherId (string | entityRef) (optional) — Optional entity id or proxy: report only the contacts with it.

Returns { PhysicsContact } — An array of PhysicsContact — empty when the body touches nothing, or when the entity carries neither a rigid body nor a soft body.

for _, c in Physics.contacts(id) do print(c.other, c.deepestPenetration) end
for _, c in Physics.contacts(truck, pole) do if c.totalImpulse > 5 then print("dented at", #c.particles, "particles") end end

globals/physics/cutSoftBody

physics.cutSoftBody(softId: string | entityRef, blade: { vec3 } | { point: vec3, normal: vec3 })

Cut a soft body along a blade, every element of the body the blade meets opening along it: { point = p, normal = n } cuts along the whole plane through p facing n, splitting the body in two wherever it lies on both sides; { a, b, c } cuts along that world-space triangle, which cuts only as far as the triangle reaches. Applied straight after the next physics step; a piece the cut separates becomes an entity of its own (see onSoftBodyTear), and the cut reports as a tear of kind "cut". A plane is measured against the body where that step leaves it, so it cuts a moving body wherever it has gone; a cut that opens nothing is reported in the log. Raises when the entity carries no SoftBody, and in edit mode, where nothing simulates.

Parameters

  • softId string | entityRef — The soft body's entity.
  • blade { vec3 } | { point: vec3, normal: vec3 } — { point = vec3, normal = vec3 } for a plane, or the triangle's three world-space corners { a, b, c }.
Physics.cutSoftBody(jelly, { point = vector.create(0, 1, 0), normal = vector.create(1, 0, 0) })   -- in two along x = 0
Physics.cutSoftBody(jelly, { vector.create(0, -5, -5), vector.create(0, 5, -5), vector.create(0, 0, 10) })
-- slice off the part of a body 0.2 m above its centre
local c = Physics.softBodyState(jelly).centerOfMass
Physics.cutSoftBody(jelly, { point = vector.create(c.x, c.y + 0.2, c.z), normal = vector.create(0, 1, 0) })

globals/physics/debugLines

physics.debugLines(opts: table?) -> (PhysicsDebugLines?, string?)

Draw what the physics world simulates beyond collider surfaces as world-space lines, with what each object's lines picture as data. This is the read the physics debug views draw from: capture { pass = "physics" } and the edit-mode collider overlay (debugViz category colliders) both show these lines. Layers: cage draws a soft body's elements coloured by load: slack blue, yellow half way, red at the load it tears (see PhysicsSoftBodyDiagnostics for how a load is read); particles draws its particles: free ones as small grey crosses, pinned ones as green octahedra, ones held to a rigid body as magenta octahedra; contacts draws the contacts soft surfaces hold against soft surfaces (their own included): the segment joining the two witness points in orange, the normal in cyan; wheels draws each WheelCollider wheel: its suspension ray as far as it reaches, its whole travel and the tyre's radius below the mount (green on ground, red in the air, as the wheel's isGrounded reads), the travel from its mount to the tyre's centre coloured by how much of it is taken up on the same ramp, its tyre circle in white across its steered axle, and its ground point (yellow) and normal (cyan). Answers in edit mode as well as play mode; in edit mode a vehicle stands at rest with its wheels where they are authored.

Parameters

  • opts table (optional) — { layers: {string}?, entities: {(string | entityRef)}? }: layers names the layers to draw (every layer when omitted; an unknown name is an error naming the layers), entities keeps to those entities' objects (a wheel is kept by its own entity or its chassis').

Returns (PhysicsDebugLines?, string?) — A PhysicsDebugLines, or nil and the reason on a VM that holds no physics world (a worker).

local d = Physics.debugLines(); print(d.lineCount, #d.objects)
for _, o in Physics.debugLines({ layers = { "particles" } }).objects do print(o.entity, o.pinned, o.attached) end
local w = Physics.debugLines({ layers = { "wheels" }, entities = { truck } }); for _, o in w.objects do print(o.index, o.grounded, o.compression) end

globals/physics/detachSoftRegion

physics.detachSoftRegion(softId: string | entityRef, targetId: (string | entityRef)?) -> number

Release regions of a soft body held with attachSoftRegion (or authored in its SoftBody component's attachments).

Parameters

  • softId string | entityRef — The soft body's entity.
  • targetId (string | entityRef) (optional) — Release only the regions held to this rigid body; omit to release every one.

Returns number — How many attachments were released.

Physics.detachSoftRegion(flag, pole)
Physics.detachSoftRegion(shell)

globals/physics/drawnPose

physics.drawnPose(entityId: string | entityRef) -> PhysicsDrawnPose?

Where an entity is drawn this frame, and where its transform puts it. The simulation advances in fixed ticks (physics.tick_rate, 30 a second by default) while frames are drawn at the display's rate, so a body with Physics.interpolation on is drawn blended between its last two ticks, and everything parented under it is drawn moved with it. entity.position reports the simulated pose, which moves only when a tick runs; this read reports the drawn one as well. Read it from a component's update to place anything that has to stay with a moving body on screen: a camera following it, a label over it, a measurement of how smoothly it moves.

Parameters

  • entityId string | entityRef — Entity id or proxy. Any entity answers; one the simulation does not blend is drawn at its simulated pose.

Returns PhysicsDrawnPose? — A PhysicsDrawnPose, or nil when nothing in the scene answers to that id.

local d = Physics.drawnPose(car); print(d.position.z, d.simulatedPosition.z, d.interpolated)
local d = Physics.drawnPose(tyre); print(d.interpolated, d.interpolatedBy)

globals/physics/getAngularVelocity

physics.getAngularVelocity(entityId: (string | entityRef)?) -> vec3?

Read the angular velocity of an entity's rigid body.

Parameters

  • entityId (string | entityRef) (optional) — Target entity id or proxy; resolves from script context when omitted.

Returns vec3? — Angular velocity in rad/s, or nil if the entity has no rigid body.

local w = Physics.getAngularVelocity(id)

globals/physics/getGravity

physics.getGravity() -> vec3

Read the current world gravity vector.

Returns vec3 — Gravity vector in m/s² (negative y is "down" in the default world).

local g = Physics.getGravity()

globals/physics/getVelocity

physics.getVelocity(entityId: (string | entityRef)?) -> vec3?

Read the linear velocity of an entity's rigid body, or of its soft body as a whole (its particles' velocities averaged by mass).

Parameters

  • entityId (string | entityRef) (optional) — Target entity id or proxy; resolves from script context when omitted.

Returns vec3? — Velocity in m/s, or nil if the entity has no rigid body.

local v = Physics.getVelocity(id)

globals/physics/getWheelState

physics.getWheelState(entityId: string | entityRef) -> table?

Read a wheel collider's runtime state. Reads the native component the wheel system writes after each physics step.

Parameters

  • entityId string | entityRef — Target entity id (must carry a WheelCollider component).

Returns table? — { isGrounded, compression, angularVelocity, rotation, skid, sideImpulse, forwardImpulse, suspensionForce, stopImpulse, center, hitPoint, hitNormal }, or nil if the component is absent. compression runs from 0 at the spring's rest up to suspensionDistance, where the bump stop is; rotation is the wheel's angle about its axle in radians, growing as it rolls forward; skid is how fast the tyre slides sideways (m/s) while its grip is used up, 0 while it holds; the impulses are what the tyre applied to the chassis this tick (N·s), and stopImpulse what the bump stop applied once the suspension was used up; center is the wheel centre in world space.

local state = Physics.getWheelState(id)

globals/physics/hasLineOfSight

physics.hasLineOfSight(fromId: string, toId: string) -> boolean

Check whether two entities have line-of-sight between their origins.

Parameters

  • fromId string — Viewer entity id.
  • toId string — Target entity id.

Returns boolean — true when no collider sits between them (including coincident origins), false otherwise.

if Physics.hasLineOfSight(a, b) then ... end

globals/physics/ignoreCollision

physics.ignoreCollision(entityIdA: string | entityRef, entityIdB: string | entityRef, ignore: boolean?)

Keep two entities from colliding, or let them collide again. Covers every collider each one owns (its rigid body with the shapes composed onto it, its standalone collider, its ragdoll's bones, its soft body's surface), so it keeps a soft shell off the chassis it is held to as well as two rigid bodies apart. An ignored pair holds no contact: the solver pushes neither one and Physics.contacts lists neither to the other. It applies from the next physics step, to bodies already touching as well, and lasts until it is turned off, either entity is despawned, or the mode changes: entering or leaving play starts without it, so make the call where the bodies are set up for play (a component's onLoad). It stands beside collision groups: a pair either one excludes does not collide.

Parameters

  • entityIdA string | entityRef — First entity id.
  • entityIdB string | entityRef — Second entity id.
  • ignore boolean (optional) — When true (default) the pair is kept apart; false lets it collide again.
Physics.ignoreCollision(a, b, true)

globals/physics/isSleeping

physics.isSleeping(entityId: (string | entityRef)?) -> boolean?

Whether an entity's rigid or soft body is currently asleep (at rest and not simulating). A body sleeps once it stops moving, to save simulation cost.

Parameters

  • entityId (string | entityRef) (optional) — Target entity id or proxy; resolves from script context when omitted.

Returns boolean? — true if asleep, false if awake, or nil if the entity has neither a rigid nor a soft body.

if Physics.isSleeping(id) then Physics.wakeUp(id) end

globals/physics/jointBreaks

physics.jointBreaks() -> table

Every joint that has broken since the last call to this function. A joint breaks when the reaction it carries exceeds the breakForce (newtons of linear reaction) or breakTorque (the angular row of the same reaction) its joint was given; each joint reports once and its constraint is already released when the record arrives. The 256 most recent are kept: a structure that comes apart while nothing reads them drops the oldest beyond that, as the engine's own queue does beyond 1024.

Returns table — Array of { entityId, connectedEntityId, kind, impulse, angularImpulse, force, torque, position }, oldest first.

for _, e in ipairs(Physics.jointBreaks()) do print(e.entityId, e.force) end

globals/physics/jointReaction

physics.jointReaction(entityId: string | entityRef) -> table?

The load an entity's joint is carrying right now, as the constraint solver resolved it on the last physics step. This is the same quantity a break threshold is measured against, so it is what to size breakForce and breakTorque from. A joint with a soft body at either end reads the same way.

Parameters

  • entityId string | entityRef — Entity carrying the Joint component.

Returns table? — { impulse, angularImpulse, force, torque, position }, or nil when the entity owns no joint.

local r = Physics.jointReaction(id); print(r and r.force)

globals/physics/observe

physics.observe(entityId: (string | entityRef)?, opts: table?) -> PhysicsObservation?

Read the solver's own state — the world's accounting, and what it holds for each body plus why it is not moving one. Every value comes off the simulation rather than the Physics component, so a write the solver refused or clamped reads back as what it kept. Answers in edit mode as well as play mode.

Parameters

  • entityId (string | entityRef) (optional) — Report on this one entity. Omit for every body in the world.
  • opts table (optional) — { bodies: boolean?, contactPoints: boolean? } — bodies = false builds the world accounting alone, and contactPoints = false keeps each contact pair's normal, depth, impulse and point count while leaving out the individual points. Both default to true.

Returns PhysicsObservation? — A PhysicsObservation, or nil when entityId names nothing in the scene. bodies is an array, not a table keyed by entity id — each entry names its own entity in entity.

local o = Physics.observe(); for _, b in o.bodies do print(b.entity, b.stillness) end
local o = Physics.observe(id); print(o.bodies[1].stillness, o.bodies[1].stillnessDetail)

globals/physics/onJointBreak

physics.onJointBreak(fn: (table) -> ()) -> () -> ()

Call fn for every joint that breaks from now on, with the same record jointBreaks returns.

Parameters

  • fn (table) -> () — Receives one break record per broken joint.

Returns () -> () — A function that removes this listener.

local off = Physics.onJointBreak(function(e) print(e.kind, e.force, e.position) end)

globals/physics/onSoftBodyTear

physics.onSoftBodyTear(fn: (PhysicsSoftBodyTear) -> ()) -> () -> ()

Call fn for every tear of a soft body from now on: a body loaded past its tearStrain or tearForce (fields of its SoftBody component), cut by Physics.cutSoftBody or torn by Physics.tearSoftBody. The torn entity keeps the largest piece; every piece split off is an entity of its own by the time fn runs, standing at the root of the scene and carrying a SoftBody with the torn body's fields, its share of the model and the material, the velocity, dents and pins that fell on its side. Its body joins it on the next frame, and an impulse applied to it in fn reaches the body then. A piece is play state: it is left out of every save, and leaving play takes it away and gives the torn entity back its whole body.

Parameters

  • fn (PhysicsSoftBodyTear) -> () — Receives one PhysicsSoftBodyTear per tear: { entity, pieces, position, kind, particles }.

Returns () -> () — A function that removes this listener.

local off = Physics.onSoftBodyTear(function(e) print(e.entity, #e.pieces, e.position) end)
Physics.onSoftBodyTear(function(e) for _, id in e.pieces do Physics.applyImpulse(id, { x = 0, y = 2, z = 0 }) end end)

globals/physics/overlapSphere

physics.overlapSphere(center: vec3, radius: number) -> table

Find every entity id whose colliders overlap a sphere. A trigger (isTrigger) holds nothing up, so it is left out.

Parameters

  • center vec3 — Sphere center in world space.
  • radius number — Sphere radius.

Returns table — Array of overlapping entity ids.

local ids = Physics.overlapSphere({x=0,y=0,z=0}, 5)

globals/physics/pinSoftRegion

physics.pinSoftRegion(softId: string | entityRef, region: PhysicsSoftRegion?, opts: { name: string?, target: vec3? }?) -> string

Pin a region of a soft body: its particles are held kinematic, where they stand now, and the rest of the body hangs from them, rests on them or swings about them through its elastic elements. A cloth pinned by two corners hangs; a jelly pinned along its base stays put while its top wobbles. Give the pin a target (or set one later with Physics.setPinTarget) and the pinned particles move together so their centre stands there, dragging the body after them: a hand grabbing a corner, a press driven down into a block. The pin is an entry of the soft body's SoftBody component (pins), so it is saved with the scene, reaches every peer and is made again on a rebuilt body; it reads back from Physics.softBodyState(softId).pins.

Parameters

  • softId string | entityRef — The soft body's entity (it carries a SoftBody component).
  • region PhysicsSoftRegion (optional) — Which particles to pin, as Physics.softRegion takes it: { box = { min, max } } or { nearest = point } in the entity's unit box (-0.5 to 0.5 on each axis), space = "world" for world coordinates; nil pins the whole body.
  • opts { name: string?, target: vec3? } (optional) — { name: string?, target: vec3? }: the name the pin is addressed by (by default pin1, pin2, ...), and a world position to move the pinned particles' centre to.

Returns string — The pin's name.

local corner = Physics.pinSoftRegion(cloth, { nearest = {-0.5, 0, 0.5} })
Physics.pinSoftRegion(cloth, { box = { min = {-0.5, -1, 0.45}, max = {0.5, 1, 0.5} } }, { name = "top" })
local grab = Physics.pinSoftRegion(jelly, { nearest = hit.point, space = "world" }, { target = hit.point })

globals/physics/pumpJointBreaks

physics.pumpJointBreaks()

Deliver every joint break the simulation has recorded to the registered listeners. An enabled Joint component calls this each tick, so listeners fire on their own wherever joints come from that component. A joint made by writing ecs.PhysicsJoint directly has no such tick behind it — call this each frame, or poll jointBreaks, to deliver its breaks.

Physics.pumpJointBreaks()

globals/physics/pumpSoftBodyTears

physics.pumpSoftBodyTears()

Give every piece the soft bodies tore off since the last call its entity, and deliver each tear to the onSoftBodyTear listeners. Every SoftBody component calls this each frame, so tears are handled on their own; the work is done once per frame however many call it.

Physics.pumpSoftBodyTears()

globals/physics/raycast

physics.raycast(origin: vec3, direction: vec3, maxDistance: number?, exclude: (string | {string})?) -> table?

Cast a ray and return the first hit. Answers from COLLIDERS ALONE: a mesh that renders but carries no collider is not in the physics world, so a ray fired through it reports the same nil a ray through open air does. renderer.raycast answers the same ray against the geometry the renderer DRAWS, which is what reads the surface of a terrain, a procedurally generated mesh, or any plain Model. A trigger (isTrigger) holds nothing up, so the ray passes through it.

Parameters

  • origin vec3 — Ray origin in world space.
  • direction vec3 — Ray direction, of any length: the engine normalises it, so maxDistance and distance are world units.
  • maxDistance number (optional) — Maximum distance along the ray (defaults to 1000).
  • exclude (string | {string}) (optional) — Optional entity id, or array of entity ids, to exclude from hits.

Returns table? — Hit table { entityId, point, normal, distance, startedInside, softBody? }, or nil if nothing hit. startedInside is false for a surface the query crossed on its way there, and true when the query's own start already lay inside that collider — where distance is 0, point is the start itself, and normal is the shortest way out of the collider. normal is a unit vector either way. softBody is set when what was hit is a soft body, whose surface the query meets as it is deformed now: a PhysicsSoftHit naming the surface triangle, the particles carrying it with the hit's weight on each, the particle nearest the hit, and region, the hit as a soft region to hand to Physics.applyImpulse, Physics.pinSoftRegion or Physics.softParticles. Physics.applyImpulseAtPoint takes the hit's point as it is. A nil says the ray met no COLLIDER, which Physics.colliderCount() separates from a world that holds none for it to meet.

local hit = Physics.raycast({x=0,y=2,z=0}, {x=0,y=-1,z=0})
local hit = Physics.raycast(origin, dir, 50, { selfId, carriedId })
local hit = Physics.raycast(muzzle, aim, 100); if hit and hit.softBody then Physics.applyImpulseAtPoint(hit.entityId, {x=0, y=0, z=-20}, hit.point, { radius = 0.3 }) end
if Physics.colliderCount() == 0 then hit = renderer.raycast(eye, down, 200) end

globals/physics/raycastAll

physics.raycastAll(origin: vec3, direction: vec3, maxDistance: number?, maxHits: number?, exclude: (string | {string})?) -> table

Cast a ray and return every hit up to maxHits. Answers from COLLIDERS ALONE, so a rendered mesh with no collider is absent from the result; renderer.raycastAll answers the same ray against the geometry the renderer draws. A trigger (isTrigger) holds nothing up, so the ray passes through it.

Parameters

  • origin vec3 — Ray origin in world space.
  • direction vec3 — Ray direction, of any length: the engine normalises it, so maxDistance and distance are world units.
  • maxDistance number (optional) — Optional distance limit along the ray.
  • maxHits number (optional) — Optional cap on the number of hits returned.
  • exclude (string | {string}) (optional) — Optional entity id, or array of entity ids, to exclude from hits.

Returns table — Array of hit tables { entityId, point, normal, distance, startedInside, softBody? } — empty when nothing was hit. startedInside is false for a surface the query crossed on its way there, and true when the query's own start already lay inside that collider — where distance is 0, point is the start itself, and normal is the shortest way out of the collider. normal is a unit vector either way. softBody is set when what was hit is a soft body, whose surface the query meets as it is deformed now: a PhysicsSoftHit naming the surface triangle, the particles carrying it with the hit's weight on each, the particle nearest the hit, and region, the hit as a soft region to hand to Physics.applyImpulse, Physics.pinSoftRegion or Physics.softParticles. Physics.applyImpulseAtPoint takes the hit's point as it is.

local hits = Physics.raycastAll(origin, dir, 50, 4)

globals/physics/raycastBetween

physics.raycastBetween(fromId: string, toId: string, maxDistance: number?) -> table?

Cast a ray from one entity toward another and return the first hit.

Parameters

  • fromId string — Origin entity id.
  • toId string — Target entity id.
  • maxDistance number (optional) — Optional distance cap (default 1000).

Returns table? — Hit table, or nil if the entities are coincident or nothing was hit.

local hit = Physics.raycastBetween(a, b)

globals/physics/raycastScreen

physics.raycastScreen(sx: number, sy: number, maxDistance: number?, exclude: (string | {string})?) -> table?

Cast a ray from a screen pixel into the scene and return the first hit. Unprojects the pixel with screenToRay, then casts with raycast.

Parameters

  • sx number — Screen X in viewport-local pixels (the space of input.mouse_position and screenToRay).
  • sy number — Screen Y in viewport-local pixels.
  • maxDistance number (optional) — Maximum distance along the ray (defaults to 1000, matching raycast).
  • exclude (string | {string}) (optional) — Optional entity id, or array of entity ids, to exclude from hits.

Returns table? — Hit table { entityId, point, normal, distance, startedInside, softBody? }, or nil on a miss or when no camera has rendered yet. startedInside is false for a surface the query crossed on its way there, and true when the query's own start already lay inside that collider — where distance is 0, point is the start itself, and normal is the shortest way out of the collider. normal is a unit vector either way. softBody is set when what was hit is a soft body, whose surface the query meets as it is deformed now: a PhysicsSoftHit naming the surface triangle, the particles carrying it with the hit's weight on each, the particle nearest the hit, and region, the hit as a soft region to hand to Physics.applyImpulse, Physics.pinSoftRegion or Physics.softParticles. Physics.applyImpulseAtPoint takes the hit's point as it is.

local Zin = require("@builtin::modules.zinput")
local x, y = Zin.state.mousePosition(); local hit = Physics.raycastScreen(x, y)

globals/physics/releasePin

physics.releasePin(softId: string | entityRef, name: string?) -> number

Release a pin of a soft body, or every pin: its particles take their mass back, keep the velocity they had, and move with the body again.

Parameters

  • softId string | entityRef — The soft body's entity.
  • name string (optional) — The pin to release; omit to release every one.

Returns number — How many pins were released.

Physics.releasePin(cloth, corner)
Physics.releasePin(cloth)

globals/physics/removeCollider

physics.removeCollider(entityId: string | entityRef) -> string?

Remove whichever collider component an entity carries.

Parameters

  • entityId string | entityRef — Target entity id.

Returns string? The component that was removed, or nil when there was none.

Physics.removeCollider(id)

globals/physics/removeConstraint

physics.removeConstraint(entityId: string | entityRef, index: number?)

Remove transform constraints from an entity (if any are present).

Parameters

  • entityId string | entityRef — Target entity id.
  • index number (optional) — Optional constraint index (currently ignored — the whole component is removed).
Physics.removeConstraint(id)

globals/physics/removeJoint

physics.removeJoint(entityId: string | entityRef)

Remove the Joint component from an entity (if present).

Parameters

  • entityId string | entityRef — Target entity id.
Physics.removeJoint(id)

globals/physics/removeWheelCollider

physics.removeWheelCollider(entityId: string | entityRef)

Remove the WheelCollider component from an entity (if present).

Parameters

  • entityId string | entityRef — Target entity id.
Physics.removeWheelCollider(id)

globals/physics/repairSoftBody

physics.repairSoftBody(softId: string | entityRef) -> number

Restore the shape a soft body was built in: every permanent dent, crumple and crease it took past its plasticYield is undone, and the body is put back in that shape where it stands, keeping its motion as a whole. The repair is the SoftBody component's repairs count raised by one, so it reaches every peer. Physics.softBodyState(softId).plastic reads 0 once it is applied (at the start of the next frame).

Parameters

  • softId string | entityRef — The soft body's entity (it carries a SoftBody component).

Returns number — The component's new repairs count.

Physics.repairSoftBody(shell)

globals/physics/setAngularDamping

physics.setAngularDamping(entityIdOrDamping: string | entityRef | number, damping: number?)

Set angular damping on an entity's rigid body. One-arg form targets the script-context entity.

Parameters

  • entityIdOrDamping string | entityRef | number — Entity id (with damping) OR damping value (script-context entity).
  • damping number (optional) — Optional explicit damping when targeting another entity.
Physics.setAngularDamping(0.1)
Physics.setAngularDamping(entityId, 0.1)

globals/physics/setAngularVelocity

physics.setAngularVelocity(a: string | entityRef | number | vec3, b: (number | vec3)?, c: number?, d: number?)

Set the angular velocity of an entity (radians/sec). Same call shapes as setVelocity.

Parameters

  • a string | entityRef | number | vec3 — x-component, a {x, y, z} vector, or an entity id (explicit target).
  • b (number | vec3) (optional) — y-component, x-component, or the vector depending on call form.
  • c number (optional) — z-component or y-component depending on call form.
  • d number (optional) — Optional z-component when targeting an explicit entity.
Physics.setAngularVelocity(0, 0, 1)
Physics.setAngularVelocity(entityId, 0, 0, 1)
Physics.setAngularVelocity(entityId, {x=0, y=0, z=1})

globals/physics/setBodyType

physics.setBodyType(entityId: string | entityRef, bodyType: string)

Change a rigid body's type at runtime. Mass, colliders, and joints are preserved — only the body's response to forces and position writes changes.

Parameters

  • entityId string | entityRef — Target entity id.
  • bodyType string — One of "dynamic", "kinematic", "static".
Physics.setBodyType(entityId, "kinematic")

globals/physics/setCcdEnabled

physics.setCcdEnabled(entityIdOrEnabled: string | entityRef | boolean, enabled: boolean?)

Enable or disable continuous collision detection on an entity's rigid body. One-arg form targets the script-context entity.

Parameters

  • entityIdOrEnabled string | entityRef | boolean — Entity id (with enabled) OR boolean (script-context entity).
  • enabled boolean (optional) — Optional explicit boolean when targeting another entity.
Physics.setCcdEnabled(true)
Physics.setCcdEnabled(entityId, true)

globals/physics/setCollisionGroups

physics.setCollisionGroups(entityId: string | entityRef, membership: number, filter: number)

Set the collision-group membership and filter bitmasks on an entity's colliders. Adds a CollisionGroup component if missing; the component holds the groups, so they apply to the colliders of the entity's rigid body or standalone collider by the next physics step, and a body built or rebuilt later takes them as it is built, in edit mode as well as play. A soft body's groups are its SoftBody component's collisionMemberships and collisionFilter.

Parameters

  • entityId string | entityRef — Target entity id.
  • membership number — Bitmask: which groups this collider belongs to.
  • filter number — Bitmask: which groups this collider can collide with.
Physics.setCollisionGroups(id, 0x0001, 0xFFFF)

globals/physics/setGravity

physics.setGravity(gravity: vec3)

Replace the world gravity vector.

Parameters

  • gravity vec3 — New gravity vector in m/s².
Physics.setGravity({x=0, y=-9.81, z=0})

globals/physics/setGravityScale

physics.setGravityScale(entityIdOrScale: string | entityRef | number, scale: number?)

Set the per-entity gravity scale (1.0 = normal, 0.0 = no gravity). One-arg form targets the script-context entity.

Parameters

  • entityIdOrScale string | entityRef | number — Entity id (with scale) OR scale value (script-context entity).
  • scale number (optional) — Optional explicit scale when targeting another entity.
Physics.setGravityScale(0.5)
Physics.setGravityScale(entityId, 0.5)

globals/physics/setJointMotor

physics.setJointMotor(entityId: string | entityRef, targetVelocity: number, maxForce: number)

Set a motor on an entity's joint.

Parameters

  • entityId string | entityRef — Target entity id (must carry a Joint component).
  • targetVelocity number — Desired joint velocity.
  • maxForce number — Maximum force the motor can apply.
Physics.setJointMotor(id, 5.0, 1000)

globals/physics/setLinearDamping

physics.setLinearDamping(entityIdOrDamping: string | entityRef | number, damping: number?)

Set linear damping on an entity's rigid body (0 = no damping). One-arg form targets the script-context entity.

Parameters

  • entityIdOrDamping string | entityRef | number — Entity id (with damping) OR damping value (script-context entity).
  • damping number (optional) — Optional explicit damping when targeting another entity.
Physics.setLinearDamping(0.05)
Physics.setLinearDamping(entityId, 0.05)

globals/physics/setMass

physics.setMass(entityIdOrMass: string | entityRef | number, mass: number?)

Set the mass of an entity's rigid body (kg). One-arg form targets the script-context entity.

Parameters

  • entityIdOrMass string | entityRef | number — Entity id (with mass) OR mass value (script-context entity).
  • mass number (optional) — Optional explicit mass when targeting another entity.
Physics.setMass(10)
Physics.setMass(entityId, 10)

globals/physics/setPinTarget

physics.setPinTarget(softId: string | entityRef, name: string, position: vec3?)

Drive a pin of a soft body to a world position: its particles move together over the next step so the centre they had when the pin was made stands at position, with the velocity that carries them there, and the rest of the body follows. Set it every frame to drag the region along a path. nil stops driving it and holds it where it stands. The target is the pin's entry in the SoftBody component's pins, so it reaches every peer.

Parameters

  • softId string | entityRef — The soft body's entity.
  • name string — The pin's name, as pinSoftRegion returned it.
  • position vec3 (optional) — The world position for the pinned particles' centre; nil holds them where they stand.
Physics.setPinTarget(cloth, corner, {x=2, y=3, z=0})

globals/physics/setRotationLocks

physics.setRotationLocks(entityId: string | entityRef, x: boolean, y: boolean, z: boolean)

Lock or unlock rotation on specific axes.

Parameters

  • entityId string | entityRef — Target entity id.
  • x boolean — Lock rotation about the world X axis.
  • y boolean — Lock rotation about the world Y axis.
  • z boolean — Lock rotation about the world Z axis.
Physics.setRotationLocks(id, false, true, false)

globals/physics/setTranslationLocks

physics.setTranslationLocks(entityId: string | entityRef, x: boolean, y: boolean, z: boolean)

Lock or unlock translation on specific axes.

Parameters

  • entityId string | entityRef — Target entity id.
  • x boolean — Lock translation along the world X axis.
  • y boolean — Lock translation along the world Y axis.
  • z boolean — Lock translation along the world Z axis.
Physics.setTranslationLocks(id, false, false, true)

globals/physics/setVelocity

physics.setVelocity(a: string | entityRef | number | vec3, b: (number | vec3)?, c: number?, d: number?)

Set the linear velocity of an entity. Accepts (x, y, z) or a {x, y, z} vector for the script-context entity, or the same prefixed with an explicit entityId.

Parameters

  • a string | entityRef | number | vec3 — x-component, a {x, y, z} vector, or an entity id (explicit target).
  • b (number | vec3) (optional) — y-component, x-component, or the vector depending on call form.
  • c number (optional) — z-component or y-component depending on call form.
  • d number (optional) — Optional z-component when targeting an explicit entity.
Physics.setVelocity(0, 10, 0)
Physics.setVelocity(entityId, 0, 10, 0)
Physics.setVelocity(entityId, {x=0, y=10, z=0})

globals/physics/softBodySettings

physics.softBodySettings(changes: table?) -> PhysicsSoftBodySettings

The settings every soft body of the world is simulated under, and the way to change them: how many extra substeps a hard hit may run, how stiff soft contacts are, which tangle and overlap recovery runs, and how far the FEM solver (SoftBody.solver = "fem") solves each step. Each one trades simulation quality for cost; Physics.stepCost().softBodies shows what each body costs under them. They are world settings (physics.soft_body_* in .world_settings) saved with the world: a change reads back at once and the solver runs under it from the next physics tick. Written during play, they land on the play shadow like any other /source write: the solver runs under them at once, and they are gone when the play edits are discarded. Write them in edit mode (or keep them with vfs.promotePlayShadow("/zero/source/.world_settings")) for the world to keep them.

Parameters

  • changes table (optional) — Optional PhysicsSoftBodySettings fields to change; the fields left out keep their values. A field the settings do not have, or a value of the wrong type or out of range, is refused by name.

Returns PhysicsSoftBodySettings — The settings with changes applied: what the next tick runs under.

print(Physics.softBodySettings().maxExtraSubsteps)
Physics.softBodySettings({ maxExtraSubsteps = 2 })   -- cheaper hard hits
Physics.softBodySettings({ tangleDetection = false, overlapConstraints = false })
Physics.softBodySettings({ femMaxIterations = 60 })   -- FEM bodies converge further each substep

globals/physics/softBodyState

physics.softBodyState(entityId: string | entityRef, opts: table?) -> PhysicsSoftBodyState?

What the solver holds for one soft body (an entity with a SoftBody component): its particle, cell and edge counts, the volume it encloses against its rest volume, its centre of mass and frame, the box around its particles, how fast they move, whether it sleeps, and what it touches, and in render how the entity's model is drawn carried by the body. plastic is how deep the deformation that stayed is, in metres (a dent past the component's plasticYield; plasticMean the mean over the particles), 0 for a body that only deformed elastically. diverged is true while a step has carried the body into a state no material reaches (its particles spread, or its volume grown, past ten times the shape it is held at, or a particle carried to a value that is not a number), and divergence says what was measured; the engine log names the body the step it diverges. tearStrain and tearForce are the thresholds it tears past (0 tears never), and piece names the torn piece a body split off another continues (nil for a body built from its fields). attachments lists the regions held to rigid bodies (attachSoftRegion) with the force each pulls with, attachedParticles counts the particles tied, and clusters counts the body's frame plus one per joint end anchored on a region of it. pins lists the regions pinned (pinSoftRegion) with how many particles each holds, its target and where its particles' centre stands, and pinnedParticles counts the particles held kinematic. diagnostics is what the physics debug views picture of the body as numbers: inverted cells, self-crossings, element loads (maxLoad, loadedElements), pinned and attached particles (see PhysicsSoftBodyDiagnostics; Physics.debugLines draws them). replication, on a synced entity in a multiplayer session, says which peer simulates the body (authority "local" or "remote", owner) and what it costs on the wire here (lastSync in seconds, bytesPerSecond). status says where the body stands: "built", "pending" (the component stands and the body is not built yet, waitingFor naming what the build waits on), "refused" (refusal says why the component was not built) or "none" (no SoftBody component). A body is built at the start of the frame after its component is added, so a read in the call that added it answers "pending"; wait on status rather than reading exists = false as a refusal. Answers in edit mode as well as play mode.

Parameters

  • entityId string | entityRef — Entity id or proxy.
  • opts table (optional) — { positions: boolean?, contactPoints: boolean?, contactsWith: (string | entityRef)? }: positions = true adds every particle's world position; contactPoints = true adds each contact's individual points (each contact names its count, normal, depth, impulse and particles either way); contactsWith reports only the contacts with that one entity, so a read about one pair costs that pair alone.

Returns PhysicsSoftBodyState? — A PhysicsSoftBodyState, or nil when nothing in the scene answers to that id.

local s = Physics.softBodyState(jelly); print(s.particleCount, s.volumeRatio, s.sleeping)
print(Physics.softBodyState(shell).plastic)   -- metres of dent the shell kept
local s = Physics.softBodyState(shell); if s.diverged then print(s.divergence) end
for _, p in Physics.softBodyState(jelly, { positions = true }).positions do print(p.y) end
local r = Physics.softBodyState(jelly).render; print(r.drawn, r.vertexCount, r.uploadedThisFrame)
local s = Physics.softBodyState(jelly); if s.status == "pending" then print("waiting for", s.waitingFor) end

globals/physics/softBodyTears

physics.softBodyTears() -> { PhysicsSoftBodyTear }

Every soft body tear since the last call to this function, oldest first, with the entities of the pieces each split off: the same records onSoftBodyTear delivers. The 256 most recent are kept.

Returns { PhysicsSoftBodyTear } — Array of PhysicsSoftBodyTear.

for _, t in Physics.softBodyTears() do print(t.kind, t.entity, #t.pieces) end

globals/physics/softParticles

physics.softParticles(softId: string | entityRef, region: PhysicsSoftRegion?) -> PhysicsSoftParticles?

Read the particles of a soft body that a region selects: where each stands, how fast it moves and whether it is pinned, with the selection's centre of mass and mean velocity. The way to see a dent where a hit landed, how far a pinned corner was dragged, or how a region moves. indices name the particles for comparing two reads of the same body. The region is resolved at each call against where the particles stand then (a local-space region in the body's frame as it stands now), so a read after the body moved can select other particles than an earlier read of the same region; a pin's own particles read back through Physics.softBodyState(softId).pins (center) and the pinned flags.

Parameters

  • softId string | entityRef — The soft body's entity.
  • region PhysicsSoftRegion (optional) — Which particles to read, as Physics.softRegion takes it; nil reads the whole body.

Returns PhysicsSoftParticles? — A PhysicsSoftParticles report, or nil when nothing in the scene answers to that id.

local top = Physics.softParticles(jelly, { box = { min = {-0.5, 0.4, -0.5}, max = {0.5, 0.5, 0.5} } }); print(top.count, top.center.y)
local near = Physics.softParticles(shell, { box = { min = {1.6, 0, -0.3}, max = {2.2, 1, 0.3} }, space = "world" })

globals/physics/softRegion

physics.softRegion(region: PhysicsSoftRegion?) -> { [string]: any }

The engine form of a soft body region: the record the SoftBody component's attachments and a Joint's soft body ends carry. A region names part of a soft body by where its particles stand: { box = { min, max } } is every particle between the two corners, { nearest = point } the one particle nearest the point, and nil (or {}) the whole body. Coordinates are in the soft body entity's local space, the unit box it is built in (-0.5 to 0.5 on each axis, before its scale), so a region stays the same part of the body wherever the body is; space = "world" reads them in world space instead. A box counts a particle whose contact skin reaches into it: a generated body's outer particles stand that skin inside the unit box, so a box flat on one of its faces (min = {-0.5, -1, -1}, max = {-0.5, 1, 1}) holds that face's particles. Errors naming the key that is wrong.

Parameters

  • region PhysicsSoftRegion (optional) — { box = { min = v, max = v } | { v, v }, nearest = v, space = "local" | "world" }, or nil for the whole body.

Returns { [string]: any } — { kind = "Whole" | "Box" | "Nearest", min, max, point, world }.

Physics.softRegion({ box = { min = {-0.5, 0.45, -0.5}, max = {0.5, 0.5, 0.5} } })
Physics.softRegion({ nearest = {0, 0.5, 0} })

globals/physics/sphereCast

physics.sphereCast(origin: vec3, radius: number, direction: vec3, maxDistance: number?, exclude: (string | {string})?) -> table?

Cast a sphere along a direction and return the first hit. A trigger (isTrigger) holds nothing up, so the sweep passes through it.

Parameters

  • origin vec3 — Sphere center at the start of the cast.
  • radius number — Sphere radius.
  • direction vec3 — Cast direction, of any length: the engine normalises it, so maxDistance and distance are world units.
  • maxDistance number (optional) — Optional distance limit.
  • exclude (string | {string}) (optional) — Optional entity id, or array of entity ids, to exclude from hits. Applied while sweeping rather than to the answer, so a cast that starts inside an excluded collider reports what is behind it.

Returns table? — Hit table { entityId, point, normal, distance, startedInside, softBody? }, or nil if nothing hit. startedInside is false for a surface the query crossed on its way there, and true when the query's own start already lay inside that collider — where distance is 0, point is the start itself, and normal is the shortest way out of the collider. normal is a unit vector either way. softBody is set when what was hit is a soft body, whose surface the query meets as it is deformed now: a PhysicsSoftHit naming the surface triangle, the particles carrying it with the hit's weight on each, the particle nearest the hit, and region, the hit as a soft region to hand to Physics.applyImpulse, Physics.pinSoftRegion or Physics.softParticles. Physics.applyImpulseAtPoint takes the hit's point as it is.

local hit = Physics.sphereCast(o, 0.5, dir, 10)
local hit = Physics.sphereCast(o, 0.5, dir, 10, selfId)

globals/physics/stepCost

physics.stepCost() -> PhysicsStepCost?

What the last physics step cost, stage by stage — the same figures worldState().step carries, for a caller that wants only these. Each covers that one step rather than a window of them, and consecutive steps over the same resting scene vary by tens of percent, so several samples averaged is the honest read of what a step costs. softBodyMs is the soft body part of the step and softBodies each soft body's part of it. A frame can run several steps; recent lists the last 64 with their ordinals (step), so a loop reading once a frame counts every step.

Returns PhysicsStepCost? — A PhysicsStepCost, or nil on a frame where the pipeline did not step — a paused simulation, or a world still bootstrapping.

local c = Physics.stepCost(); if c then print(c.stepMs, c.narrowPhaseMs) end
local c = Physics.stepCost(); if c then for _, b in c.softBodies do print(b.entity, b.ms, b.particles) end end
local seen, total = 0, 0
-- once a frame: every step since the last read, however many a frame ran
local c = Physics.stepCost(); if c then for _, s in c.recent do if s.step > seen then total += s.softBodyMs; seen = s.step end end end

globals/physics/stillnessReasons

physics.stillnessReasons() -> { string }

Every reason whyStill can answer with, in the order the engine considers them. Read from the engine, so the list is the one the answers come from.

Returns { string } — An array of reason names.

for _, reason in Physics.stillnessReasons() do print(reason) end

globals/physics/tearSoftBody

physics.tearSoftBody(softId: string | entityRef, region: PhysicsSoftRegion)

Tear a soft body at a region: every edge and cell holding a particle of the region comes apart there, opening a hole or, when the region runs across the body, splitting it. Applied straight after the next physics step; a piece the tear separates becomes an entity of its own (see onSoftBodyTear), and it reports as a tear of kind "tear". Raises when the entity carries no SoftBody, for a region naming no box and no point (the whole body), and in edit mode, where nothing simulates.

Parameters

  • softId string | entityRef — The soft body's entity.
  • region PhysicsSoftRegion — Which particles to tear through, as Physics.softRegion takes it: { box = { min, max } } or { nearest = point } in the entity's unit box (-0.5 to 0.5 on each axis), space = "world" for world coordinates.
Physics.tearSoftBody(cloth, { nearest = hit.point, space = "world" })
Physics.tearSoftBody(sheet, { box = { min = {-0.02, -1, -0.5}, max = {0.02, 1, 0.5} } })

globals/physics/touching

physics.touching(entityId: string | entityRef, otherId: string | entityRef) -> (boolean, number, { PhysicsContactPoint })

Whether two entities are touching, and how deeply.

Parameters

  • entityId string | entityRef — Entity id or proxy.
  • otherId string | entityRef — The other entity id or proxy.

Returns (boolean, number, { PhysicsContactPoint }) — (touching, deepestPenetration, points) — deepestPenetration is in metres and 0 for surfaces that meet without overlapping.

local hit, depth = Physics.touching(a, b); print(hit, depth)

globals/physics/vehicleState

physics.vehicleState(chassisId: string | entityRef) -> table?

Read a vehicle: the chassis body's forward speed and the state of every wheel under it (the wheels whose nearest rigid-body ancestor is this chassis).

Parameters

  • chassisId string | entityRef — The chassis entity (the rigid body the wheels sit under).

Returns table? — { speed, groundedWheels, wheels }, or nil when the chassis has no WheelCollider under it. speed is metres per second along the chassis' forward (-Z), negative in reverse; wheels lists getWheelState tables, each with its entityId, in hierarchy order.

local car = Physics.vehicleState(carId); print(car.speed, car.groundedWheels)

globals/physics/wakeUp

physics.wakeUp(entityId: (string | entityRef)?)

Wake an entity's sleeping rigid or soft body so it resumes simulating. The motion setters (applyImpulse, setVelocity, setAngularVelocity) wake the body for you; call this to wake one explicitly.

Parameters

  • entityId (string | entityRef) (optional) — Target entity id or proxy; resolves from script context when omitted.
Physics.wakeUp(id)

globals/physics/whyStill

physics.whyStill(entityId: string | entityRef) -> (string?, string?)

Why the solver is not moving a body. Returns nil when it IS moving it, and otherwise one of noBody, simulationNotStepping, disabled, static, kinematic, infiniteMass, translationLocked, gravityDisabled, asleep, outsideIsland, resting, aboutToMove — the nearest cause, so the answer names the thing to change. A second return carries the detail: which collider it rests on and how deeply, what its effective gravity works out to, and so on.

Parameters

  • entityId string | entityRef — Entity id or proxy.

Returns (string?, string?) — (reason, detail).

local why, detail = Physics.whyStill(id); if why then print(why, detail) end

globals/physics/worldState

physics.worldState() -> PhysicsWorldState

How many bodies, colliders, joints and contacts the simulation holds right now, with world gravity, the timestep, whether the pipeline is stepping at all, and what the last step cost. Counted off the solver, so a body that failed to build is absent here while its Physics component still exists.

Returns PhysicsWorldState — A PhysicsWorldState.

local w = Physics.worldState(); print(w.bodies.awake .. "/" .. w.bodies.total .. " awake")
print(Physics.worldState().contacts.touchingPairs .. " pairs touching")
  • api
  • reference