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.
| Key | Action |
|---|---|
W / R / T | Move / rotate / scale (scale only on a target with a size) |
Q | World / local space |
LMB | Grab a handle |
Left Alt | Snap to the ground |
B | Toggle the position grid (free movement is the default) |
N | Cycle the grid size: 0.05, 0.1, 0.25, 0.5, 1 meter |
Shift | Rotating: 15 degree steps. Scaling: grow both sides |
Ctrl | Scaling: finer steps |
G / Enter | Confirm (only with manageControls) |
Backspace / Esc | Cancel (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 resultTargets
- Entity: any entity handle. Peds are made see-through while edited, other entities get an outline (
outline = falseturns it off). - Virtual target: a table
{ coords, rotation?, heading?, size?, shape? }.rotationis pitch / roll / yaw in degrees (the orderGetEntityRotation(entity, 2)uses),sizeis the full extents (width along right, length along forward, height along up) and enables scale.shapeis'box'(default),'plane'(flat: forward is its normal) or'sphere'(one size).
Functions
| Call | Description |
|---|---|
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
| Option | Description |
|---|---|
manageControls | Disable 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. |
outline | false for invisible placement helpers. |
gridSnap, gridSize | Start with the grid on, and the nearest supported size. |
modes, mode | Handle sets the keys can switch between, and the one to start in. |
size | Entity targets: the size the scale handles resize. |
shape | What size describes ('box', 'plane', 'sphere'). |
yawOnly | Rotate offers only the ring around the up axis (zones). |
minSize, maxSize | Limits for scale handles (default minimum 0.05 m). |
snap, fineSnap | Scale step in meters (default 0.05) and the step while Ctrl is held (default a fifth). |
drawBounds | Draw the target's box while moving or rotating too. |
groundOffset | Virtual 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
onChangetold), 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 setignoreEditorLock).