Physics
Physics namespace — collisions, joints, raycasts, body manipulation. Auto-injected by the prelude from @builtin::modules.api.engine.physics. Same table as the lowercase physics alias.
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
entityIdstring | entityRef— Target entity id.componentstring— One ofPhysics.COLLIDER_COMPONENTS.configtable(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
entityIdstring | entityRef— Target entity id.optstable(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
entityIdAstring | entityRef— Entity that hosts the Joint component.entityIdBstring | entityRef— Connected entity.optstable(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 aSoftBodycomponent):regionanchors this end on a region of entity A's soft body andremoteRegionthe other end on a region of B's, each asPhysics.softRegiontakes 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
astring | 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.cnumber(optional) — dz or dy depending on call form.dnumber(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
entityIdstring | entityRef— Target entity id.configtable(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
entityIdOrForcestring | entityRef | vec3— Entity id (when paired withforce) OR a force vector for the script-context entity.forcevec3(optional) — Optional force vector when targeting an explicit entity.regionPhysicsSoftRegion(optional) — Part of a soft body to push, asPhysics.softRegiontakes 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
entityIdstring | entityRef— Target entity id.forcevec3— Force vector, in newtons.pointvec3— World-space application point.optsPhysicsPushOptions(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
entityIdOrImpulsestring | entityRef | vec3— Entity id (withimpulse) OR an impulse vector for the script-context entity.impulsevec3(optional) — Optional impulse vector when targeting an explicit entity.regionPhysicsSoftRegion(optional) — Part of a soft body to push, asPhysics.softRegiontakes 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
entityIdstring | entityRef— The body's entity id or proxy.impulsevec3— The impulse, in newton-seconds.pointvec3— Where it lands, in world space: a contact point.optsPhysicsPushOptions(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
entityIdstring | entityRef— The body's entity id or proxy.centervec3— The blast's world position.magnitudenumber— The impulse at the centre, in newton-seconds.optsPhysicsPushOptions(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
entityIdOrTorquestring | entityRef | vec3— Entity id (withtorque) OR a torque vector for the script-context entity.torquevec3(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
softIdstring | entityRef— The soft body's entity (it carries aSoftBodycomponent).targetIdstring | entityRef— The rigid body's entity (it carriesPhysics), by id, name or proxy.regionPhysicsSoftRegion(optional) — Which particles to hold, asPhysics.softRegiontakes it:{ box = { min, max } },{ nearest = point }, withspace = "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
entityIdstring | 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
originvec3— Box center at the start of the cast.halfExtentsvec3— Half the size of the box on each axis.directionvec3— Cast direction, of any length: the engine normalises it, somaxDistanceanddistanceare world units.maxDistancenumber(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
originvec3— Capsule centre at the start of the cast.radiusnumber— Capsule radius.halfHeightnumber— Distance from the centre to either cap centre. The capsule standshalfHeight + radiustall in each direction.directionvec3— Cast direction, of any length: the engine normalises it, somaxDistanceanddistanceare world units.maxDistancenumber(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
optionstable(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
entityIdstring | 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
entityIdstring | 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
entityIdstring | 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
softIdstring | 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
optstable(optional) —{ layers: {string}?, entities: {(string | entityRef)}? }:layersnames the layers to draw (every layer when omitted; an unknown name is an error naming the layers),entitieskeeps 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
softIdstring | 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
entityIdstring | 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
entityIdstring | 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
fromIdstring— Viewer entity id.toIdstring— 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
entityIdAstring | entityRef— First entity id.entityIdBstring | entityRef— Second entity id.ignoreboolean(optional) — Whentrue(default) the pair is kept apart;falselets 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
entityIdstring | 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.optstable(optional) —{ bodies: boolean?, contactPoints: boolean? }—bodies = falsebuilds the world accounting alone, andcontactPoints = falsekeeps 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 onePhysicsSoftBodyTearper 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
centervec3— Sphere center in world space.radiusnumber— 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
softIdstring | entityRef— The soft body's entity (it carries aSoftBodycomponent).regionPhysicsSoftRegion(optional) — Which particles to pin, asPhysics.softRegiontakes 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 defaultpin1,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
originvec3— Ray origin in world space.directionvec3— Ray direction, of any length: the engine normalises it, somaxDistanceanddistanceare world units.maxDistancenumber(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
originvec3— Ray origin in world space.directionvec3— Ray direction, of any length: the engine normalises it, somaxDistanceanddistanceare world units.maxDistancenumber(optional) — Optional distance limit along the ray.maxHitsnumber(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
fromIdstring— Origin entity id.toIdstring— Target entity id.maxDistancenumber(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
sxnumber— Screen X in viewport-local pixels (the space ofinput.mouse_positionandscreenToRay).synumber— Screen Y in viewport-local pixels.maxDistancenumber(optional) — Maximum distance along the ray (defaults to 1000, matchingraycast).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
softIdstring | entityRef— The soft body's entity.namestring(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
entityIdstring | 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
entityIdstring | entityRef— Target entity id.indexnumber(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
entityIdstring | 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
entityIdstring | 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
softIdstring | entityRef— The soft body's entity (it carries aSoftBodycomponent).
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
entityIdOrDampingstring | entityRef | number— Entity id (withdamping) OR damping value (script-context entity).dampingnumber(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
astring | 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.cnumber(optional) — z-component or y-component depending on call form.dnumber(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
entityIdstring | entityRef— Target entity id.bodyTypestring— 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
entityIdOrEnabledstring | entityRef | boolean— Entity id (withenabled) OR boolean (script-context entity).enabledboolean(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
entityIdstring | entityRef— Target entity id.membershipnumber— Bitmask: which groups this collider belongs to.filternumber— 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
gravityvec3— 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
entityIdOrScalestring | entityRef | number— Entity id (withscale) OR scale value (script-context entity).scalenumber(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
entityIdstring | entityRef— Target entity id (must carry a Joint component).targetVelocitynumber— Desired joint velocity.maxForcenumber— 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
entityIdOrDampingstring | entityRef | number— Entity id (withdamping) OR damping value (script-context entity).dampingnumber(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
entityIdOrMassstring | entityRef | number— Entity id (withmass) OR mass value (script-context entity).massnumber(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
softIdstring | entityRef— The soft body's entity.namestring— The pin's name, aspinSoftRegionreturned it.positionvec3(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
entityIdstring | entityRef— Target entity id.xboolean— Lock rotation about the world X axis.yboolean— Lock rotation about the world Y axis.zboolean— 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
entityIdstring | entityRef— Target entity id.xboolean— Lock translation along the world X axis.yboolean— Lock translation along the world Y axis.zboolean— 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
astring | 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.cnumber(optional) — z-component or y-component depending on call form.dnumber(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
changestable(optional) — OptionalPhysicsSoftBodySettingsfields 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
entityIdstring | entityRef— Entity id or proxy.optstable(optional) —{ positions: boolean?, contactPoints: boolean?, contactsWith: (string | entityRef)? }:positions = trueadds every particle's world position;contactPoints = trueadds each contact's individual points (each contact names its count, normal, depth, impulse and particles either way);contactsWithreports 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
softIdstring | entityRef— The soft body's entity.regionPhysicsSoftRegion(optional) — Which particles to read, asPhysics.softRegiontakes 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
regionPhysicsSoftRegion(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
originvec3— Sphere center at the start of the cast.radiusnumber— Sphere radius.directionvec3— Cast direction, of any length: the engine normalises it, somaxDistanceanddistanceare world units.maxDistancenumber(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
softIdstring | entityRef— The soft body's entity.regionPhysicsSoftRegion— Which particles to tear through, asPhysics.softRegiontakes 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
entityIdstring | entityRef— Entity id or proxy.otherIdstring | 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
chassisIdstring | 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
entityIdstring | 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")