Skip to content

Gizmo (tm.gizmo) ​

Client side only. The in-world move / rotate / scale gizmo, usable from any resource. One gizmo per resource at a time: starting a new one ends the previous (its onClose fires with reason 'replaced').

tm.gizmo never uses the game's built-in gizmo. The handles are drawn with DrawLine / DrawPoly and picked with a ray from the mouse cursor by the internal engine tm.gizmonative, so nothing depends on a console command, a permission or another resource.

KeyAction
W / R / TMove / rotate / scale (scale only on a target with a size)
QWorld / local space
LMBGrab a handle
Left AltSnap to the ground
BToggle the position grid (free movement is the default)
NCycle the grid size: 0.05, 0.1, 0.25, 0.5, 1 meter
ShiftRotating: 15 degree steps. Scaling: grow both sides
CtrlScaling: finer steps
G / EnterConfirm (only with manageControls)
Backspace / EscCancel (only with manageControls)

The keys are rebindable key mappings, registered per importing resource the first time a gizmo starts. Their labels are the locale keys gizmo.keys.*.

lua
tm.gizmo.start(entity, { onClose = function(cancelled, reason) end })
tm.gizmo.stop()   -- confirm
tm.gizmo.cancel() -- discard, the target goes back where it started

local result = tm.gizmo.use(entity) -- blocking: runs until G / Enter, returns { handle, position, rotation, cancelled }

-- A virtual target has no entity: the gizmo edits the table in place (and puts it back on cancel).
local zone = { coords = vec3(0, 0, 70), heading = 90.0, size = vec3(4, 2, 3), shape = 'box' }
tm.gizmo.use(zone, { onChange = function(t) end }) -- zone.coords / .rotation / .heading / .size now hold the result

Targets ​

  • Entity: any entity handle. Peds are made see-through while edited, other entities get an outline (outline = false turns it off).
  • Virtual target: a table { coords, rotation?, heading?, size?, shape? }. rotation is pitch / roll / yaw in degrees (the order GetEntityRotation(entity, 2) uses), size is the full extents (width along right, length along forward, height along up) and enables scale. shape is 'box' (default), 'plane' (flat: forward is its normal) or 'sphere' (one size).

Functions ​

CallDescription
start(target, opts?)Starts a session without blocking. Returns false when the target is missing or has no coords.
stop()Ends the session as a confirmed edit. Takes effect on the next frame.
cancel()Ends it as a cancel (onClose(true, 'escape')).
use(target, opts?)Blocking: sets manageControls = true unless you pass it, runs until confirm or cancel and returns the result (nil when it could not start).
setSuspended(state)Releases the cursor and ignores edits while true (menu focus, free look).
setMode('translate'|'rotate'|'scale')Switches the handle set when the session allows it.
toggleSpace() / toggleGrid() / cycleGridSize()Same as the keys.
isActive()A session is running.
isNative()true while a session runs (the native handles are the only engine).
getState(){ enabled, mode, isRelative, suspended, gridSnap, gridSize, native, canScale }.

Options ​

OptionDescription
manageControlsDisable game controls from the gizmo loop (standalone use) and hold tm.inputlock for the session. Without it the caller disables controls.
controls{ toggle?, confirm?, cancel? } control ids for manageControls (default G, Enter, Backspace). Esc always cancels.
constrain(coords)Clamp the position after every edit, return a vector3.
onChange(target)After every edit, with { coords, rotation, heading, size? } (the virtual target itself).
onStateChange()The mode, space or grid changed.
onClose(cancelled, reason)Once, when the session ends. Reasons: 'confirm', 'escape', 'cancel', 'error', 'lost' (the entity vanished), 'replaced', 'resourceStop'.
onSnap()Called instead of the built-in ground snap.
outlinefalse for invisible placement helpers.
gridSnap, gridSizeStart with the grid on, and the nearest supported size.
modes, modeHandle sets the keys can switch between, and the one to start in.
sizeEntity targets: the size the scale handles resize.
shapeWhat size describes ('box', 'plane', 'sphere').
yawOnlyRotate offers only the ring around the up axis (zones).
minSize, maxSizeLimits for scale handles (default minimum 0.05 m).
snap, fineSnapScale step in meters (default 0.05) and the step while Ctrl is held (default a fifth).
drawBoundsDraw the target's box while moving or rotating too.
groundOffsetVirtual targets: how far above the ground the center rests after a snap.

Behavior ​

  • Handles keep a constant size on screen. Move has arrows, plane squares and a center cube that drags in the screen plane. Rotate has three rings. Scale has a cube on every face (the opposite face stays put) and a center cube that scales everything.
  • A cancelled session puts the target back: a virtual table is rewritten (and onChange told), an entity gets its matrix back.
  • A session owns one thread that only runs while a gizmo is open. Nothing runs or draws while idle.
  • If the engine errors, the error is logged and the session ends as a cancel with reason 'error'. Errors inside your callbacks are logged and never end or break the session.
  • When the resource stops, the session closes (onClose(true, 'resourceStop')): the cursor, the entity's look, the input lock and the target are all put back.
  • The gizmo's own keys keep working while an editor holds tm.inputlock (they set ignoreEditorLock).